package cn.net.mugui.net.udp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;

import org.apache.commons.lang3.StringUtils;

import com.mugui.base.util.Other;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.digest.MD5;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

@Getter
@Setter
@Accessors(chain = true)
public class UdpBag {

    /**
     * 2: 发送bag的hash信息。表示将要发送此包，如果未收到（3）反馈将再次发送此消息。<br>
     */
    public static final byte type_2 = 2;
    /**
     * 3: 反馈bag的hash信息。表示可接收，如果未收到（4,5）切片包的消息反馈将再次发送此消息。<br>
     */
    public static final byte type_3 = 3;
    /**
     * 4: 切片包的发送，<br>
     * 5: 普通包的发送，分包情况下表示为最后一个切片发送完成<br>
     */
    public static final byte type_4 = 4;
    /**
     * 4: 切片包的发送，<br>
     * 5: 普通包的发送，分包情况下表示为最后一个切片发送完成<br>
     */
    public static final byte type_5 = 5;
    /**
     * ping
     */
    public static final byte type_0 = 0;
    /**
     * ret_ping
     */
    public static final byte type_1 = 1;
    /**
     * 6: 包接送成功, 由接收方反馈自己接收到了这个包。（发送方若未收到，将发送2。）<br>
     */
    public static final byte type_6 = 6;
    private static final MD5 md5 = MD5.create();
    /**
     * 包类型：<br>
     * 以下为连通性测试<br>
     * 0: ping 消息。确认是否可以沟通（发送方）<br>
     * 1: ret ping 消息。反馈ping消息，表示可以沟通。（接收方反馈）<br>
     * ***********************************<br>
     * 以下为必达包的消息处理<br>
     * 2: 发送bag的hash信息。表示将要发送此包，如果未收到（3）反馈将再次发送此消息。<br>
     * 3: 反馈bag的hash信息。表示可接收，如果未收到（4,5）切片包的消息反馈将再次发送此消息。<br>
     * 4: 切片包的发送，<br>
     * 5: 普通包的发送，分包情况下表示为最后一个切片发送完成<br>
     * （4,5）接收方未接收则发送3消息<br>
     * 6: 包接送成功, 由接收方反馈自己接收到了这个包。（发送方若未收到，将发送2。）<br>
     * 7: 发送方向接收方表明。已知晓 接收方 接收。(如果接收方未收到7，则发送6，再次确认。)<br>
     * 8: 接收方表明通信结束。（如未收到8，则发送6。）如果发送方最后未收到8，也未收到其他消息。将视为消息成功。<br>
     * 以上为必达消息的包<br>
     * 9: 非必达消息包类型<br>
     */
    private byte type;
    /**
     * 当前包的hash也是当前包的唯一标识
     */
    private byte[] bag_hash;

	/**
	 * 直达消息
	 */
	public static final byte bag_type_0 = 0;
	/**
	 * 广播消息
	 */
	public static final byte bag_type_1 = 1;
	/**
	 * 包传输类型
	 */
	private byte bag_type;

    /**
     * 数据切片序号，默认从0 开始
     */
    private byte data_index;
    /**
     * 数据包hash,也是该数据包的唯一标识
     */
    private byte[] data_hash;
    /**
     * 数据包
     */
    private byte[] data;
    /**
     * 包的来源方
     */
    private byte[] from;
    /**
     * 包的接收方，也即是包数据的公钥加密
     */
    private byte[] to;
    /**
     * 包的创建时间
     */
    private long time = System.currentTimeMillis();

    /**
     * 将bag_data解析为一个UdpBag
     *
     * @param bag_data
     * @return
     * @author 木鬼
     */
    public static UdpBag resolve(byte[] bag_data) {
        UdpBag udpBag = new UdpBag();
        try {
            udpBag.type = bag_data[0];
            byte bool = bag_data[1];
            if (bool == 1) {
                byte[] time = new byte[8];
                System.arraycopy(bag_data, 2, time, 0, 8);
                udpBag.time = Other.ByteArrayToLong(time);
                byte[] bag_hash = new byte[16];
                System.arraycopy(bag_data, 10, bag_hash, 0, 16);
                udpBag.bag_hash = bag_hash;
                udpBag.data_index = bag_data[26];
                if (udpBag.data_index > 0) {
                    byte[] data_hash = new byte[16];
                    System.arraycopy(bag_data, 27, data_hash, 0, 16);
                    udpBag.data_hash = data_hash;
                    byte[] data_length = new byte[4];
                    System.arraycopy(bag_data, 43, data_length, 0, 4);
                    int byteArrayint = Other.byteArrayint(data_length);
                    byte[] data = new byte[byteArrayint];
                    System.arraycopy(bag_data, 47, data, 0, byteArrayint);
                    udpBag.data = data;
                }
            }
            int length = udpBag.length();
			udpBag.bag_type = bag_data[length-1];
			int len = bag_data.length - length;
            if (len != 0 && len != 50) {
                new RuntimeException("包数据长度错误");
            }
            if (len == 50) {
                byte[] from = new byte[25];
                System.arraycopy(bag_data, bag_data.length - 50, from, 0, 25);
                udpBag.from = from;
                byte[] to = new byte[25];
                System.arraycopy(bag_data, bag_data.length - 25, to, 0, 25);
                udpBag.to = to;
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return udpBag;
    }

    /**
     * 通过发送数据新建一个data
     *
     * @param data 发送数据
     * @return
     * @author 木鬼
     */
    public static UdpBag newBag(Object data) {
        return newBag(data, null, null);
    }

    /**
     * d42c40b94d253e558d0f863e73e8a34b6ec4a331
     *
     * @param data 发送数据
     * @param from 来源，
     * @param to   去向
     * @return
     * @author 木鬼
     */
    public static UdpBag newBag(Object data, String from, String to) {
        UdpBag udpBag = new UdpBag();
        if (data != null) {
            String string = data.toString();
            udpBag.data = string.getBytes();
            udpBag.data_hash = md5.digest(string);
            udpBag.checkLength(udpBag.data_hash, 16, "data_hash");
            udpBag.data_index = 1;
        }
        String data_hex = md5.digestHex(udpBag.data_hash) + udpBag.time;
        if (StringUtils.isNotBlank(from) && StringUtils.isNotBlank(to)) {
            udpBag.from = from.getBytes();
            udpBag.checkLength(udpBag.from, 25, "from");
            udpBag.to = to.getBytes();
            udpBag.checkLength(udpBag.to, 25, "to");
            data_hex += md5.digestHex(udpBag.from) + md5.digestHex(udpBag.to);
        }
        udpBag.bag_hash = md5.digest(data_hex);
        udpBag.type = UdpBag.type_5;
		udpBag.bag_type = UdpBag.bag_type_0;
        return udpBag;
    }

    public int length() {
        int i = 2;
        if (bag_hash != null) {
            i += 8;
            checkLength(bag_hash, 16, "bag_hash");
            i += bag_hash.length;
            i += 1;
            if (data_index > 0) {
                checkLength(data_hash, 16, "data_hash");
                i += data_hash.length;
                i += 4;
                i += data.length;
            }
        }
		i += 1;
        if (from != null) {
            checkLength(from, 25, "from");
            i += from.length;
            checkLength(to, 25, "to");
            i += to.length;
        }
        return i;
    }

    private void checkLength(byte[] bytes, int i, String data_name) {
        if (bytes == null || bytes.length != i) {
            throw new RuntimeException(
                    "checkLength 错误：关于参数" + data_name + " 长度应为：" + i + " 当前：" + (bytes != null ? bytes.length : 0));
        }
    }

    /**
     * 将数据装换为bytes
     *
     * @return
     * @author 木鬼
     */
    public byte[] bytes() {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            byteArrayOutputStream.write(Other.intToByteArray(length()));
            byteArrayOutputStream.write(type);
            if (bag_hash != null) {
                byteArrayOutputStream.write(1);
                byteArrayOutputStream.write(Other.LongToByteArray(time));
                byteArrayOutputStream.write(bag_hash);
                byteArrayOutputStream.write(data_index);
                if (data_index > 0) {
                    byteArrayOutputStream.write(data_hash);
                    byteArrayOutputStream.write(Other.intToByteArray(data.length));
                    byteArrayOutputStream.write(data);
                }
            } else {
                byteArrayOutputStream.write(0);
            }
			byteArrayOutputStream.write(bag_type);
            if (from != null) {
                byteArrayOutputStream.write(from);
                byteArrayOutputStream.write(to);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return byteArrayOutputStream.toByteArray();
    }
//	private static final Digester sha1=SecureUtil.sha1();

    @Override
    public String toString() {
        StringWriter writer = new StringWriter();
        writer.append("type=").append(String.valueOf(type)).append(" ");
        if (bag_hash != null) {
            writer.append("time=").append(String.valueOf(time)).append(" ");
            writer.append("bag_hash=").append(HexUtil.encodeHexStr(bag_hash)).append(" ");
            writer.append("data_index=").append(String.valueOf(data_index)).append("\n");
            if (data_hash != null) {
                writer.append("data_hash=").append(HexUtil.encodeHexStr(data_hash)).append(" ");
                writer.append("data=").append(new String(data)).append("\n");
            }
        }
		writer.append("bag_type=").append(String.valueOf(bag_type)).append(" ");
        if (from != null) {
            writer.append("from=").append(new String(from)).append(" ");
        }
        if (to != null) {
            writer.append("to=").append(new String(to)).append(" ");
        }
        return writer.toString();
    }
}
